use std::{
    fs, path::PathBuf,
    collections::{
        HashMap, BTreeSet,
    },
};

use crate::report;

#[derive(Debug)]
#[allow(dead_code)]
struct Params {
    confs: HashMap<String, String>,
    switchs: BTreeSet<String>,
    vars: Vec<String>,
}

impl Params {
    fn switch_of(&self, name:&str, default:bool) -> bool {
        if self.switchs.contains(name) {
            true
        } else {
            default
        }
    }

    fn conf_get(&self, name:&str) -> String {
        if self.confs.contains_key(name) {
            self.confs.get(name).unwrap().clone()
        } else {
            String::new()
        }
    }

    fn vars(&self) -> &Vec<String> {
        &self.vars
    }

    fn from_args(args:&[String]) -> Self {
        let mut confs:HashMap<String, String> = HashMap::new();
        let mut switchs:BTreeSet<String> = BTreeSet::new();
        let mut vars:Vec<String> = Vec::new();
        let mut it = args.iter();
        while let Some(a) = it.next() {
            if a.as_bytes()[0] == ('-' as u8) {
                match a.split_once('=') {
                    Some((k,v)) => {
                        if v.len() > 0 {
                            let key = String::from(&k[1..]);
                            confs.insert(key, String::from(v));
                        } else {
                            report::error_and_quit(format!("option '{}' it lask value", a));
                        }
                    }
                    None => {
                        let switch_name = &a[1..];
                        if switch_name.len() > 0 {
                            switchs.insert(String::from(switch_name));
                        }
                    }
                }
            } else {
                vars.push(a.clone()); break
            }
        }
        while let Some(a) = it.next() {
            vars.push(a.clone())
        }
        Self {
            confs, switchs, vars
        }
    }
}


#[derive(Debug,Clone)]
pub enum OptionSyncMode {
    Default,
    AutoRemove,
    PushToFrom,
}

#[derive(Debug,Clone)]
pub struct Option {
    pub sync_mode:OptionSyncMode,
    pub allow_recurive_dir:bool,
    pub terminate_if_sync_error:bool,
    pub is_silent:bool,
    pub use_hash_diff_file:bool,
    pub paths:Vec<PathBuf>,
}

impl Option {
    pub fn new() -> Option {
        Option {
            sync_mode: OptionSyncMode::Default,
            allow_recurive_dir: false,
            terminate_if_sync_error: false,
            is_silent: false,
            use_hash_diff_file: false,
            paths: Vec::new(),
        }
    }

    fn adjust(&mut self, par:&Params) -> &mut Self {
        match par.conf_get("m").as_str() {
            "remove" => {self.sync_mode = OptionSyncMode::AutoRemove}
            "push" => {self.sync_mode = OptionSyncMode::PushToFrom}
            _ => {}
        }
        self.terminate_if_sync_error = par.switch_of("e", self.terminate_if_sync_error);
        self.allow_recurive_dir = par.switch_of("nr", self.allow_recurive_dir);
        self.is_silent = par.switch_of("q", self.is_silent);
        self.use_hash_diff_file = par.switch_of("hash", self.use_hash_diff_file);
        self
    }
    pub fn parse_args(&mut self, args:&[String]) -> &mut Self {
        assert!(self.paths.is_empty());
        let par = Params::from_args(args);
        let mkdir_if_dirto_not_exist = par.switch_of("c", false);
        for path in par.vars() {
            let path = PathBuf::from(path);
            if ! path.is_dir() {
                if (self.paths.len() > 0) && mkdir_if_dirto_not_exist {
                    if let Err(e) = fs::create_dir(&path) {
                        report::error_and_quit(format!("{}",e))
                    }
                } else {
                    report::error_and_quit(format!("{:?} not a dir-path.", path))
                }
            }
            if self.paths.iter().any(|x| x == &path) {
                report::error_and_quit(format!("{:?} path has repeat.", path))
            }
            self.paths.push(path);
        }
        self.adjust(&par)
    }
}
